/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package br.ufpr.inf.escel.testing.tools;

import br.ufpr.inf.escel.brain.model.DadoTeste;
import br.ufpr.inf.escel.brain.model.Individual;
import br.ufpr.inf.escel.brain.model.pp.DadoTestePP;
import br.ufpr.inf.escel.consts.Constantes;
import br.ufpr.inf.escel.util.Writer;
import br.ufpr.inf.escel.testing.Console;
import br.ufpr.inf.escel.testing.model.Cut;
import br.ufpr.inf.escel.testing.model.CutPP;
import br.ufpr.inf.escel.testing.tools.impl.Ferramenta;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author gustavo
 */
public class Poketool extends Ferramenta {

    private Console bash;
    private CutPP cut;
    private boolean hasExecutavel = false;
    private boolean wasInstrumented = false;

    public Poketool() {
        bash = Console.getInstance();
    }

    @Override
    public double run(Individual ind) {
        Writer.it("Executing poketool test cases. ind" + ind.getId());
        instrumentar();
        executarDadoTeste(ind);

        if (Constantes.FITNESS_DADO_TESTE.equals("POKETOOL")) {
            for (DadoTeste dado : ind.getDadosTeste()) {
                DadoTeste novoDado = avaliarDadoTeste((DadoTestePP) dado);
                ind.update(dado, novoDado);
            }
        }

        return avaliarIndividuo(ind);
    }

    private void instrumentar() {
        if (!wasInstrumented) {
            StringBuffer poketool = new StringBuffer();

            //poketool programa.c -fmetodo1 -fmetodo2 ... -fmetodoN
            poketool.append("poketool ");
            poketool.append(cut.getPrograma());
            poketool.append(cut.getFuncoes("exec"));

            //lista de comandos a serem executados
            String[] comandos = {
                "cd " + cut.getNovoPath(),
                poketool.toString()
            };

            bash.run(comandos);
            gerarExecutavel();
            wasInstrumented = true;
        }
    }

    /**
     * Gera executavel do cut .c
     * @param cut
     * @since 4.7
     */
    private void gerarExecutavel() {
        if (!hasExecutavel) {
            String[] gerarExecutavel = {
                "cd " + cut.getNovoPath(),
                cut.gerarExecutavel("poke"),
                "mkdir tcase"
            };
            bash.run(gerarExecutavel);
            hasExecutavel = true;
        }
    }

    /**
     * Metodo que realiza a execução do dado de teste no programa executável.
     * @param dadoTeste
     * @param numIndividuo
     * @param numDadoTeste
     * @return tempo de execução do dado de teste
     */
    private Long executarDadoTeste(DadoTestePP dado, int numIndividuo) {

        //o arquivo case será do dado de teste, ou do parametro
        String tcase = (dado.getDado().equals("")) ? dado.getParametro() : dado.getDado();

        criarArquivoCase(cut.getNovoPath(), tcase, numIndividuo, dado.getId());

        String[] exec = {
            "cd " + cut.getNovoPath(),
            "./exec-poke " + dado.getParametro() + "< tcase/ind" + numIndividuo + "/case" + dado.getId()
        };

        long inicio = System.currentTimeMillis();
        bash.run(exec);
        long fim = System.currentTimeMillis();

        renomearArquivoTes(cut.getNovoPath(), cut.getPrograma(), dado.getId());

        return fim - inicio;
    }

    /**
     * Após o dado de teste ser executado, um arquivo .tes é criado pela
     * poketool com as informações sobre esse dado de teste. É preciso
     * renomea-lo sempre em cada execução, para que a avaliação seja
     * sempre do dado de teste executavel
     * @param path
     * @param executavel
     * @param index
     */
    private void renomearArquivoTes(String pastaRaiz, String programa, int index) {
        programa = programa.split("\\.")[0];

        String[] moverArquivoTes = {
            "mv " + pastaRaiz + programa + "/path.tes " +
            pastaRaiz + programa + "/path" + index + ".tes "
        };

        bash.run(moverArquivoTes);
    }

    /**
     * Cria um arquivo de texto com a informação do dado de teste
     * @param dt
     * @param index
     */
    private void criarArquivoCase(String path, String dt, int numIndividual, int numDadoTeste) {

        String[] comandos = {
            "cd " + path + "tcase",
            "mkdir ind" + numIndividual,
            "echo '" + dt + "' > case" + numDadoTeste,
            "mv case" + numDadoTeste + " " + path + "tcase/ind" + numIndividual
        };

        bash.run(comandos);
    }

    /**
     * Para cada individuo, executa todos os dados de teste na tentativa
     * de obter, futuramente, uma maior cobertura na cut.
     * @param cut
     * @param populacao
     * @since 4.7
     */
    private long executarDadoTeste(Individual ind) {
        long tempoExecucao = 0;
        //individuos
        for (int numDado = 0; numDado < ind.getCapacidade(); numDado++) {
            DadoTestePP dadoTeste = (DadoTestePP) ind.getDadoTeste(numDado);
            dadoTeste.setId(numDado + 1);
            dadoTeste.setIndividuoId(ind.getId());
            tempoExecucao = +executarDadoTeste(dadoTeste, ind.getId());
        }
        return tempoExecucao;
    }

    /**
     * Avaliação para o dado de teste
     * @param dado
     * @return dadoTeste modificado
     */
    public DadoTeste avaliarDadoTeste(DadoTestePP dado) {
        StringBuffer pokeaval = new StringBuffer();
        //pokeaval -dfuncao -criterio 1 to N
        pokeaval.append("pokeaval ").append(cut.getFuncoes("aval"));
        pokeaval.append("-").append(cut.getCriterio());
        pokeaval.append(" ").append(dado.getId()).append(" to ").append(dado.getId());

        String[] comandos = {
            "cd " + cut.getNovoPath(),
            pokeaval.toString()
        };
        String resultado = bash.run(comandos);

        dado.setFitness(extrairResultado(resultado));

        return dado;
    }

    /**
     * Avaliação para o individuo
     * @param ind
     * @return score do individuo
     */
    public Double avaliarIndividuo(Individual ind) {
        StringBuffer pokeaval = new StringBuffer();
        //pokeaval -dfuncao -criterio 1 to N
        pokeaval.append("pokeaval ").append(cut.getFuncoes("aval"));
        pokeaval.append("-").append(cut.getCriterio());
        pokeaval.append(" ").append(1).append(" to ").append(ind.getCapacidade());

        String[] comandos = {
            "cd " + cut.getNovoPath(),
            pokeaval.toString()
        };
        String resultado = bash.run(comandos);

        return extrairResultado(resultado);
    }

    /**
     * Retira de toda a informação do proveniente do avaliarDadoTeste,
     * somente o que interesse para o fitness.
     * @param resultado
     * @return fitness do dado corrente
     * @since 4.7
     */
    private Double extrairResultado(String resultado) {
        String fitness = "0";

        if (resultado.contains("=")) {
            int inicio = resultado.lastIndexOf("=");
            fitness = resultado.substring(inicio + 2, resultado.length());
        }

        return Double.parseDouble(fitness);
    }

    /**
     * Extrai os elementos não executados pelo dado de teste
     * @param resultado
     * @return list dos não executavei
     */
    public List<String> extrairNaoExecutados(String resultado) {
        List<String> elementosNaoExecutados = new ArrayList<String>();

        if (resultado.contains("executadas:")) {

            int inico = resultado.lastIndexOf("executadas:");
            int fim = resultado.lastIndexOf(">");
            resultado = resultado.substring(inico + 13, fim + 1);

            int lastI = 0;
            for (int i = 0; i < resultado.length(); i++) {
                if (resultado.charAt(i) == '\n') {
                    elementosNaoExecutados.add(resultado.substring(lastI, i));
                    lastI = i;
                }
            }
        }

        return elementosNaoExecutados;
    }

    public void setCut(Cut cut) {
        this.cut = (CutPP) cut;

        if (!this.cut.getFuncoes().contains(Constantes.FUNCAO)) {
            this.cut.addFuncao(Constantes.FUNCAO);
        }

        bash.setNovoDirCut(cut.getNovoPath());
    }
}
